package top.amot.forceview.force

import top.amot.forceview.layout.I_Layout
import top.amot.forceview.layout.QuadTree_Old

import top.amot.forceview.layout.TreeNode
import top.amot.forceview.model.AbstractNode
import kotlin.math.sqrt

class ForceCollide : AbstractForce() {
    private var radius: NodeCalculation? = null
    private var strength = DEFAULT_STRENGTH
    private var iterations = DEFAULT_ITERATIONS
    private lateinit var radii: FloatArray
    private var ri = 0.0f
    private var xi = 0.0f
    private var yi = 0.0f
    private var ri2 = 0.0f
    private var currentNode: AbstractNode? = null
    override fun initialize(layout: I_Layout) {
        super.initialize(layout)
        initialize()
    }

    override fun apply(alpha: Float) {
        /*for (k in 0 until iterations) {
             QuadTree_Old.create(nodes, { node -> x(node) }) { node -> y(node) }.let {
                 it.visitAfter(::prepare)
                 nodes.forEach { node->
                     ri = radii[node.index]
                     ri2 = ri * ri
                     xi = node.x + node.vx
                     yi = node.y + node.vy

                     it.visit(::applyVisit)
                 }
            }
        }*/

        var tree: QuadTree_Old
        for (k in 0 until iterations) {
            tree = QuadTree_Old.create(nodes, { node: AbstractNode -> x(node) }) { node: AbstractNode -> y(node) }
                .visitAfter { node: TreeNode, x0:Float, y0: Float, x1: Float, y1: Float ->
                    prepare(
                        node,
                        x0,
                        y0,
                        x1,
                        y1
                    )
                }
            for (i in nodes.indices) {
                currentNode = nodes[i]
                ri = radii[currentNode!!.index]
                ri2 = ri * ri
                xi = currentNode!!.x + currentNode!!.vx
                yi = currentNode!!.y + currentNode!!.vy
                tree.visit { node: TreeNode, x0: Float, y0: Float, x1: Float, y1: Float ->
                    applyVisit(
                        node,
                        x0,
                        y0,
                        x1,
                        y1
                    )
                }
            }
        }
    }

    fun radius(c: NodeCalculation?): ForceCollide {
        radius = c
        return this
    }

    fun iterations(iterations: Int): ForceCollide {
        this.iterations = iterations
        return this
    }

    fun strength(strength: Float): ForceCollide {
        this.strength = strength
        return this
    }

    private fun initialize() {
        radii = FloatArray(nodes.size)
        nodes.forEach {
            radii[it.index] = radius(it)
        }
    }

    private fun prepare(
        node: TreeNode,
        x0: Float,
        y0: Float,
        x1: Float,
        y1: Float
    ): Boolean {
        node.data?.let {
            node.r = radii[it.index]
            return false
        }

        node.r = 0.0f
        for (i in 0..3) {
            node.quadrants[i]?.let {
                if (it.r > node.r)
                   node.r = it.r
            }
        }
        return false
    }

    private fun applyVisit(
        node: TreeNode,
        x0: Float,
        y0: Float,
        x1: Float,
        y1: Float
    ): Boolean {
        val data = node.data
        var rj = node.r
        var r = ri + rj
        if (data != null) {
            if (data.index > node.index) {
                var x = xi - data.x - data.vx
                var y = yi - data.y - data.vy
                var l = x * x + y * y
                if (l < r * r) {
                    if (x == 0.0f) {
                        x = jiggle()
                        l += x * x
                    }
                    if (y == 0.0f) {
                        y = jiggle()
                        l += y * y
                    }
                    l = sqrt(l)
                    l = (r - l) / l * strength
                    x *= l
                    rj *= rj
                    r = rj / (ri2 + rj)
                    currentNode!!.vx += x * r
                    y *= l
                    currentNode!!.vy += y * r
                    r = 1 - r
                    data.vx -= x * r
                    data.vy -= y * r
                }
            }
            return false
        }
        return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r
    }

    private fun radius(node: AbstractNode): Float {
        if (radius == null) {
            return DEFAULT_RADIUS
        }
        val r = radius!!.calculateNode(node)
        if (r < 0) {
            return -r
        }
        return if (r == 0.0f) {
            DEFAULT_RADIUS
        } else r
    }

    companion object {
        const val NAME = "Collide"
        private const val DEFAULT_RADIUS = 1.0f
        private const val DEFAULT_STRENGTH = 1.0f
        private const val DEFAULT_ITERATIONS = 1
        private fun x(node: AbstractNode?): Float {
            return if (node != null) {
                node.x + node.vx
            } else 0.0f
        }

        private fun y(node: AbstractNode?): Float {
            return if (node != null) {
                node.y + node.vy
            } else 0.0f
        }
    }
}